﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Ahresty
{
    /// <summary>
    /// 同一部材番号でグルーピングした要素を操作するクラス
    /// </summary>
    internal class GroupUnitHandler
    {
        #region Methods

        /// <summary>
        /// 孤立要素を修正する
        /// </summary>
        internal static void FixIsolatedUnits(ref IN3DData in3dData, byte targetMaterialID, byte airMaterialID)
        {
            int[, ,] groupData = new int[in3dData.MaxX, in3dData.MaxY, in3dData.MaxZ];
            //NewGroupUnitHandler.FixIsolatedUnits(ref in3dData, targetMaterialID ,airMaterialID);

            //P1：グルーピング処理
            Console.WriteLine(String.Format("P1 Start.------{0}", DateTime.Now.ToString("HH:mm:ss")));
            GroupUnitHandler.GroupUnits(in3dData, false, ref groupData);

            //P2：各部材番号の最大グループを検出
            Console.WriteLine(String.Format("P2 Start.------{0}", DateTime.Now.ToString("HH:mm:ss")));
            List<int> mainGroupIDList = GroupUnitHandler.GetMainGroups(in3dData, groupData);

            //P3：最大グループ以外の要素をTEMPの部材番号に変更
            Console.WriteLine(String.Format("P3 Start.------{0}", DateTime.Now.ToString("HH:mm:ss")));
            GroupUnitHandler.MarkTempUnits(mainGroupIDList, ref in3dData, ref groupData);

            //P4：TEMP部材の要素をグルーピング
            Console.WriteLine(String.Format("P4 Start.------{0}", DateTime.Now.ToString("HH:mm:ss")));
            GroupUnitHandler.GroupUnits(in3dData, true, ref groupData);

            //P5：金型部材に隣接するTEMPグループの要素を金型に修正；その他は空気層に修正
            Console.WriteLine(String.Format("P5 Start.------{0}", DateTime.Now.ToString("HH:mm:ss")));
            GroupUnitHandler.ConnectIsolatedUnits(ref in3dData, groupData, targetMaterialID, airMaterialID);
        }

        /// <summary>
        /// 要素をグルーピングする
        /// </summary>
        private static void GroupUnits(IN3DData in3dData, bool isTempUnits, ref int[, ,] groupData)
        {
            int groupIndex = 0;
            short maxX = in3dData.MaxX;
            short maxY = in3dData.MaxY;
            short maxZ = in3dData.MaxZ;

            List<int> groupIndexList = new List<int>();
            groupIndexList.Capacity = maxX * maxY * maxZ;

            #region P1
            for (short z = 0; z < maxZ; z++)
                for (short y = 0; y < maxY; y++)
                    for (short x = 0; x < maxX; x++)
                    {
                        byte xPlus = x + 1 == maxX ? (byte)' ' : in3dData.Data[x + 1, y, z];
                        byte xMinus = x - 1 < 0 ? (byte)' ' : in3dData.Data[x - 1, y, z];
                        byte yPlus = y + 1 == maxY ? (byte)' ' : in3dData.Data[x, y + 1, z];
                        byte yMinus = y - 1 < 0 ? (byte)' ' : in3dData.Data[x, y - 1, z];
                        byte zPlus = z + 1 == maxZ ? (byte)' ' : in3dData.Data[x, y, z + 1];
                        byte zMinus = z - 1 < 0 ? (byte)' ' : in3dData.Data[x, y, z - 1];

                        int xPlusG = x + 1 == maxX ? 0 : groupData[x + 1, y, z];
                        int xMinusG = x - 1 < 0 ? 0 : groupData[x - 1, y, z];
                        int yPlusG = y + 1 == maxY ? 0 : groupData[x, y + 1, z];
                        int yMinusG = y - 1 < 0 ? 0 : groupData[x, y - 1, z];
                        int zPlusG = z + 1 == maxZ ? 0 : groupData[x, y, z + 1];
                        int zMinusG = z - 1 < 0 ? 0 : groupData[x, y, z - 1];

                        byte value = in3dData.Data[x, y, z];

                        if (xPlusG != 0 && value == xPlus)
                        {
                            groupData[x, y, z] = xPlusG;
                            groupIndexList.Add(xPlusG);
                            continue;
                        }
                        if (xMinusG != 0 && value == xMinus)
                        {
                            groupData[x, y, z] = xMinusG;
                            groupIndexList.Add(xMinusG);
                            continue;
                        }
                        if (yPlusG != 0 && value == yPlus)
                        {
                            groupData[x, y, z] = yPlusG;
                            groupIndexList.Add(yPlusG);
                            continue;
                        }
                        if (yMinusG != 0 && value == yMinus)
                        {
                            groupData[x, y, z] = yMinusG;
                            groupIndexList.Add(yMinusG);
                            continue;
                        }
                        if (zPlusG != 0 && value == zPlus)
                        {
                            groupData[x, y, z] = zPlusG;
                            groupIndexList.Add(zPlusG);
                            continue;
                        }
                        if (zMinusG != 0 && value == zMinus)
                        {
                            groupData[x, y, z] = zMinusG;
                            groupIndexList.Add(zMinusG);
                            continue;
                        }

                        if (isTempUnits)
                            groupIndex--;
                        else
                            groupIndex++;
                        groupData[x, y, z] = groupIndex;
                        groupIndexList.Add(groupIndex);
                    }
            #endregion

            #region P2
            for (short z = 0; z < maxZ; z++)
                for (short y = 0; y < maxY; y++)
                    for (short x = 0; x < maxX; x++)
                    {
                        byte xPlus = x + 1 == maxX ? (byte)' ' : in3dData.Data[x + 1, y, z];
                        byte xMinus = x - 1 < 0 ? (byte)' ' : in3dData.Data[x - 1, y, z];
                        byte yPlus = y + 1 == maxY ? (byte)' ' : in3dData.Data[x, y + 1, z];
                        byte yMinus = y - 1 < 0 ? (byte)' ' : in3dData.Data[x, y - 1, z];
                        byte zPlus = z + 1 == maxZ ? (byte)' ' : in3dData.Data[x, y, z + 1];
                        byte zMinus = z - 1 < 0 ? (byte)' ' : in3dData.Data[x, y, z - 1];

                        int xPlusG = x + 1 == maxX ? 0 : groupData[x + 1, y, z];
                        int xMinusG = x - 1 < 0 ? 0 : groupData[x - 1, y, z];
                        int yPlusG = y + 1 == maxY ? 0 : groupData[x, y + 1, z];
                        int yMinusG = y - 1 < 0 ? 0 : groupData[x, y - 1, z];
                        int zPlusG = z + 1 == maxZ ? 0 : groupData[x, y, z + 1];
                        int zMinusG = z - 1 < 0 ? 0 : groupData[x, y, z - 1];

                        byte value = in3dData.Data[x, y, z];

                        int valueG = groupData[x, y, z];
                        if (value == xPlus && valueG != xPlusG)
                            for (int n = 0; n < groupIndexList.Count; n++)
                                if (groupIndexList[n] == Math.Max(valueG, xPlusG))
                                {
                                    groupIndexList[n] = Math.Min(valueG, xPlusG);

                                    int z1 = n / (maxX * maxY);
                                    int y1 = (n % (maxX * maxY)) / maxX;
                                    int x1 = (n % (maxX * maxY)) % maxX;
                                    groupData[x1, y1, z1] = Math.Min(valueG, xPlusG);
                                }

                        valueG = groupData[x, y, z];
                        if (value == xMinus && valueG != xMinusG)
                            for (int n = 0; n < groupIndexList.Count; n++)
                                if (groupIndexList[n] == Math.Max(valueG, xMinusG))
                                {
                                    groupIndexList[n] = Math.Min(valueG, xMinusG);

                                    int z1 = n / (maxX * maxY);
                                    int y1 = (n % (maxX * maxY)) / maxX;
                                    int x1 = (n % (maxX * maxY)) % maxX;
                                    groupData[x1, y1, z1] = Math.Min(valueG, xMinusG);
                                }

                        valueG = groupData[x, y, z];
                        if (value == yPlus && valueG != yPlusG)
                            for (int n = 0; n < groupIndexList.Count; n++)
                                if (groupIndexList[n] == Math.Max(valueG, yPlusG))
                                {
                                    groupIndexList[n] = Math.Min(valueG, yPlusG);

                                    int z1 = n / (maxX * maxY);
                                    int y1 = (n % (maxX * maxY)) / maxX;
                                    int x1 = (n % (maxX * maxY)) % maxX;
                                    groupData[x1, y1, z1] = Math.Min(valueG, yPlusG);
                                }

                        valueG = groupData[x, y, z];
                        if (value == yMinus && valueG != yMinusG)
                            for (int n = 0; n < groupIndexList.Count; n++)
                                if (groupIndexList[n] == Math.Max(valueG, yMinusG))
                                {
                                    groupIndexList[n] = Math.Min(valueG, yMinusG);

                                    int z1 = n / (maxX * maxY);
                                    int y1 = (n % (maxX * maxY)) / maxX;
                                    int x1 = (n % (maxX * maxY)) % maxX;
                                    groupData[x1, y1, z1] = Math.Min(valueG, yMinusG);
                                }

                        valueG = groupData[x, y, z];
                        if (value == zPlus && valueG != zPlusG)
                            for (int n = 0; n < groupIndexList.Count; n++)
                                if (groupIndexList[n] == Math.Max(valueG, zPlusG))
                                {
                                    groupIndexList[n] = Math.Min(valueG, zPlusG);

                                    int z1 = n / (maxX * maxY);
                                    int y1 = (n % (maxX * maxY)) / maxX;
                                    int x1 = (n % (maxX * maxY)) % maxX;
                                    groupData[x1, y1, z1] = Math.Min(valueG, zPlusG);
                                }

                        valueG = groupData[x, y, z];
                        if (value == zMinus && valueG != zMinusG)
                            for (int n = 0; n < groupIndexList.Count; n++)
                                if (groupIndexList[n] == Math.Max(valueG, zMinusG))
                                {
                                    groupIndexList[n] = Math.Min(valueG, zMinusG);

                                    int z1 = n / (maxX * maxY);
                                    int y1 = (n % (maxX * maxY)) / maxX;
                                    int x1 = (n % (maxX * maxY)) % maxX;
                                    groupData[x1, y1, z1] = Math.Min(valueG, zMinusG);
                                }
                    }
            #endregion
        }

        /// <summary>
        /// 各部材番号の最大グループを検出する
        /// </summary>
        private static List<int> GetMainGroups(IN3DData in3dData, int[, ,] groupData)
        {
            List<GroupInfo> groupList = new List<GroupInfo>();

            for (short z = 0; z < in3dData.MaxZ; z++)
                for (short y = 0; y < in3dData.MaxY; y++)
                    for (short x = 0; x < in3dData.MaxX; x++)
                    {
                        byte mid = in3dData.Data[x, y, z];
                        int gid = groupData[x, y, z];

                        GroupInfo gi = groupList.FirstOrDefault(g => g.GroupIndex == gid && g.MaterialID == mid);
                        if (gi == null)
                            groupList.Add(new GroupInfo { MaterialID = mid, GroupIndex = gid, UnitCount = 1, });
                        else
                            gi.UnitCount++;
                    }

            var mainGroups = (from g in groupList group g by g.MaterialID into g2 select new { MaterialID = g2.Key, UnitCount = g2.Max(g4 => g4.UnitCount), }).ToArray();

            List<int> mainGroupIDList = new List<int>();
            foreach (var group in groupList)
                if (mainGroups.Count(g => g.MaterialID == group.MaterialID && g.UnitCount == group.UnitCount) > 0)
                    mainGroupIDList.Add(group.GroupIndex);

            return mainGroupIDList;
        }

        /// <summary>
        /// 最大グループ以外の要素にテンポラリの部材番号を付ける
        /// </summary>
        private static void MarkTempUnits(List<int> mainGroupIDList, ref IN3DData in3dData, ref int[, ,] groupData)
        {
            for (short z = 0; z < in3dData.MaxZ; z++)
                for (short y = 0; y < in3dData.MaxY; y++)
                    for (short x = 0; x < in3dData.MaxX; x++)
                        if (!mainGroupIDList.Contains(groupData[x, y, z]))
                        {
                            groupData[x, y, z] = 0;
                            in3dData.Data[x, y, z] = (byte)'T';
                        }
        }

        /// <summary>
        /// 金型部材に隣接するテンポラリグループの要素を金型に修正し、その他は空気層に修正する
        /// </summary>
        private static void ConnectIsolatedUnits(ref IN3DData in3dData, int[, ,] groupData, byte targetMaterialID, byte airMaterialID)
        {
            List<int> connectedGroupList = new List<int>();

            short maxX = in3dData.MaxX;
            short maxY = in3dData.MaxY;
            short maxZ = in3dData.MaxZ;

            for (short z = 0; z < maxZ; z++)
                for (short y = 0; y < maxY; y++)
                    for (short x = 0; x < maxX; x++)
                    {
                        if (in3dData.Data[x, y, z] != 'T')
                            continue;

                        int groupIndex = groupData[x, y, z];
                        if (connectedGroupList.Contains(groupIndex))
                            continue;

                        byte xPlus = x + 1 == maxX ? (byte)' ' : in3dData.Data[x + 1, y, z];
                        byte xMinus = x - 1 < 0 ? (byte)' ' : in3dData.Data[x - 1, y, z];
                        byte yPlus = y + 1 == maxY ? (byte)' ' : in3dData.Data[x, y + 1, z];
                        byte yMinus = y - 1 < 0 ? (byte)' ' : in3dData.Data[x, y - 1, z];
                        byte zPlus = z + 1 == maxZ ? (byte)' ' : in3dData.Data[x, y, z + 1];
                        byte zMinus = z - 1 < 0 ? (byte)' ' : in3dData.Data[x, y, z - 1];

                        if (xPlus == targetMaterialID || xMinus == targetMaterialID ||
                            yPlus == targetMaterialID || yMinus == targetMaterialID ||
                            zPlus == targetMaterialID || zMinus == targetMaterialID)
                            connectedGroupList.Add(groupIndex);
                    }

            for (short z = 0; z < maxZ; z++)
                for (short y = 0; y < maxY; y++)
                    for (short x = 0; x < maxX; x++)
                    {
                        if (in3dData.Data[x, y, z] != 'T')
                            continue;

                        int groupIndex = groupData[x, y, z];

                        if (connectedGroupList.Contains(groupIndex))
                            in3dData.Data[x, y, z] = targetMaterialID;
                        else
                            in3dData.Data[x, y, z] = airMaterialID;
                    }
        }

        #endregion
    }

    /// <summary>
    /// 部材番号で纏めた要素グループを表すクラス
    /// </summary>
    internal class GroupInfo
    {
        /// <summary>
        /// 材料番号
        /// </summary>
        internal byte MaterialID { get; set; }

        /// <summary>
        /// グループのインデックス
        /// </summary>
        internal int GroupIndex { get; set; }

        /// <summary>
        /// グループに含まれた要素の数
        /// </summary>
        internal int UnitCount { get; set; }
    }
}
